using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;

using BizElements.Core.ProviderComponents;

namespace BizElements.Core
{
	#region Documentation.
	/// <summary>
	/// Data-access object. Provides standard data-access functionalities to objects that implement <see cref="IEntity"/> interface.
	/// </summary>
	/// <remarks><p>Entity DAO requires an instance of IEntity (see <see cref="IEntityDAO.Entity"/>) to work. 
	/// This is the target entity to which results of data retrieval are stored in (eg. CRUD methods). It also serves 
	/// as a source of data for other methods (eg. <see cref="Find(int)"/>).</p>
	/// <p>Main features:
	/// <list type="bullet">
	/// <item>CRUD methods. Create, retrieve, update and delete methods that manipulate a single row in the 
	/// database using primary key logic. All CRUD methods have a suffix <b>One.</b></item>
	/// <item>Retrieval of parent entities using foreign keys mapped in IDbTable meta classes.</item>
	/// <item>Retrieval of child entities using one-to-many and many-to-many relations.</item>
	/// <item>Batch manipulation and retrieval of multiple rows.</item>
	/// <item>Aggregate functions.</item>
	/// <item>Sorting of retrieved rows on database or in application.</item>
	/// <item>Connection and transaction sharing with other data-tier objects.</item>
	/// <item>Hierarchical queries and updates - methods with a suffix <b>Tree</b>.</item>
	/// </list></p></remarks>
    /// <seealso cref="IEntityDAO"/>
    /// <seealso cref="EntityDaoExtensions"/>
	#endregion
	public class EntityDAO : DaoBase, IEntityDAO
    {
        #region Members and protected properties.

        /// <summary>
		/// Entity with which the current DAO is working.
		/// </summary>
		IEntity entity;

        OrderByClause sorter;

        /// <summary>
        /// Sorter used by the data retrieval methods.
        /// </summary>
        protected OrderByClause Sorter
        {
            get { return this.sorter; }
            set { this.sorter = value; }
        }

        bool sortOnDatabase = true;

		/// <summary>
		/// Specifies whether data retrieval methods will sort data on database or in DataTable after data is fetched.
		/// This is just a hint. Actual methods may ignore this.
		/// </summary>
        protected bool SortOnDatabase
        {
            get { return this.sortOnDatabase; }
            set { this.sortOnDatabase = value; }
        }

		#endregion

		#region Constructors.

		/// <summary>
		/// Initializes a new instances of EntityDAO class.
		/// </summary>
		/// <param name="entityToWorkWith">Entity with which DAO works. Null is not allowed. Depending on operation DAO 
		/// may only read entity, but it may also change it.</param>
		public EntityDAO(IEntity entityToWorkWith) 
            : base(entityToWorkWith.Table.Catalog)
		{
			this.Entity = entityToWorkWith;
		}

		/// <summary>
		/// Initializes a new instances of EntityDAO class.
		/// </summary>
		/// <param name="entityToWorkWith">Entity with which DAO works. Null is not allowed. Depending on operation DAO 
		/// may only read entity, but it may also change it.</param>
		/// <param name="conn">Connection provider the DAO will use to perform data-access operations. Null is not allowed.</param>
		public EntityDAO(IEntity entityToWorkWith, IConnectionProvider conn) 
            : base(conn)
		{
			this.Entity = entityToWorkWith;
		}

		/// <summary>
		/// Initializes a new instances of EntityDAO class.
		/// </summary>
		/// <param name="table">Table accessed by the new DAO. A new empty entity is created for the table.</param>
		public EntityDAO(IDbTable table) 
            : base(table.Catalog)
		{
			this.Entity = table.NewEntity();
		}

		/// <summary>
		/// Initializes a new instances of EntityDAO class.
		/// </summary>
		/// <param name="table">Table accessed by the new DAO. A new empty entity is created for the table.</param>
		/// <param name="conn">Connection provider the DAO will use to perform data-access operations. Null is not allowed.</param>
		public EntityDAO(IDbTable table, IConnectionProvider conn) 
            : base(conn)
		{
			this.Entity = table.NewEntity();
		}

		#endregion

		private IEntityDAO GetDAO(IEntity entity)
		{
            return EntityDaoFactory.GetEntityDAO(entity, this.ConnectionProvider);			
		}

		#region Properties.

		/// <summary>
		/// Gets or sets entity with which DAO works. Null is not allowed.
		/// </summary>
		/// <remarks>Depending on operation DAO may only read entity, but it may also change it.
		/// CRUD operations both read and update entity.</remarks>
		public IEntity Entity
		{
			get
			{
				return this.entity;
			}
			set
			{
				if (value == null)
                    throw new ArgumentNullException("value", Messages.EntityDAO_EntityMayNotBeNull);
					
				this.entity = value;
			}
		}

        /// <summary>Gets table where the entities managed by current instance are stored. Equal to table referenced by entity in <see cref="Entity"/> property.</summary>
        public IDbTable Table
        {
            get { return this.Entity.Table; }
        }

		#endregion

		#region Sorting.

		#region Documentation.
		/// <summary>
		/// Sets the sorter used by the data retrieval methods.
		/// </summary>
		/// <param name="sorter">Sort clause. May be <b>null</b>.</param>
		/// <example>
		/// The following examples selects all employees sorted by their last name.
		/// <code>
		/// public DataTable SelectAllEmployeesOrderedByLastName()
		/// {
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 	EntityDAO dao = new EntityDAO(emp);
		/// 	// Order by last name. Use ascending order (true). 	
		/// 	dao.SetSorter(new OrderByClause(emp.Table.LastName, true));
		/// 	DataTable allEmployees = dao.SelectAll();
		/// 	return allEmployees;
		/// }
		/// </code>
		/// </example>
		#endregion
		public void SetSorter(OrderByClause sorter)
		{
			Sorter = sorter;
		}

		/// <summary>
		/// Sets the sort mode for data retrieveal methods. This is just a hint. Data retrieval methods may ignore this.
		/// </summary>
		/// <param name="sortOnDatabase">Specifies whether sorting will take place on database or in DataTable after data has been fetched.</param>
		public void SetSortMode(bool sortOnDatabase)
		{
			SortOnDatabase = sortOnDatabase;
		}

		#endregion

        // Entity methods.

		#region Entity data access methods (CRUD). Affect a single row. Suffix "One".

		#region Documentation.
		/// <summary>
		/// Deletes one existing row in the database based on the primary key and marks the entity as <see cref="EntityState.Deleted"/>.
		/// </summary>
		/// <returns>Number of rows affected.</returns>
		/// <remarks>Throws exception if the parent table doesn't have a primary key constraint 
		/// or if the primary key fields of the current entity are not set.</remarks>
		/// <example>
		/// The following example deletes a row in the Employees table that has a given ID.
		/// <code>
		/// public void DeleteEmployee(int id)
		/// {
		/// 	// Create an entity, set the primary key and delete it from the database.
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 	emp.EmployeeID = id;
		/// 	
		/// 	EntityDAO dao = new EntityDAO(emp);
		/// 	dao.DeleteOne();
		/// }
		/// </code>
		/// </example>
		#endregion
		public virtual int DeleteOne()
		{
            CommandExecutionStatistics lastExecutedCommandInfo;
            int numberOfRowsAffected = DaoUtil.DeleteOne(this.ConnectionProvider, this.Entity, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return numberOfRowsAffected;
		}

		#region Documentation.
		/// <summary>
		/// Inserts one new row into the database. Affects the current entity state.
		/// </summary>
		/// <returns>Autoidentity value for the inserted row. <b>null</b> if the table doesn't contain an 
		/// autoidentity field or the DBMS specific code cannot be generated.</returns>
		/// <remarks><p>Marks the entity as <see cref="EntityState.Synchronized"/> if the insert operation 
		/// succeeded and the auto-increment value assigned by the database has been retrieved (if such a field 
		/// exists). The entity is marked as <see cref="EntityState.OutOfSync"/> if auto-increment value
		/// cannot be retrieved.</p>
		/// <p>Automatically generates the code that retrieves the new identity for supported databases. 
		/// DBMS specific code depends on the class' ConnectionProvider.</p></remarks>
		/// <example>
		/// The following example inserts a row into the Employees table and retrieves the new auto-identity value assigned to it.
		/// <code>
		/// public int InsertEmployee(string firstName, string lastName)
		/// {
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 	emp.FirstName = firstName;
		/// 	emp.LastName = lastName;
		/// 	
		/// 	// InsertOne method automatically retrieves the auto-identity value assigned to the inserted row.
		/// 	// The EmployeeID property is automatically set, too.
		/// 	EntityDAO dao = new EntityDAO(emp);
		/// 	return dao.InsertOne();			
		/// }
		/// </code>
		/// </example>
		#endregion
		public virtual object InsertOne()
		{
            CommandExecutionStatistics lastExecutedCommandInfo;
            object newId = DaoUtil.InsertOne(this.ConnectionProvider, this.Entity, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return newId;
		}

		#region Documentation.
		/// <summary>
		/// Selects one existing entity from the database based on the Primary Key, populates entity with the 
		/// retrieved data and marks it as <see cref="EntityState.Synchronized"/>.
		/// </summary>
		/// <returns>True if select succeeded; false if primary key is not defined for this table, 
		/// if the primary key values are not set or if the entity does not exist.</returns>
		/// <remarks>This method does <b>not</b> prefetch parent entities.</remarks>
		/// <example>
		/// The following example fetches an employee with the given ID.
		/// <code>
		/// public EmployeesEntity SelectEmployee(int id)
		/// {
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 	emp.EmployeeID = id;
		/// 	
		/// 	// SelectOne() method returns true if a row with the the specified ID exists.
		/// 	// It also initializes all members with the data retrieved.
		/// 	EntityDAO dao = new EntityDAO(emp);
		/// 	bool isSuccess = dao.SelectOne();
		/// 
		/// 	// If SelectOne() method found a row that matches the given ID then return the entity; otherwise return null.
		/// 	if (isSuccess)
		/// 		return emp;
		/// 	else
		/// 		return null;
		/// }
		/// </code>
		/// </example>
		#endregion
		public virtual bool SelectOne()
		{
            CommandExecutionStatistics lastExecutedCommandInfo;
            bool isFetched = DaoUtil.SelectOne(this.ConnectionProvider, this.Entity, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return isFetched;
		}		

		#region Documentation.
		/// <summary>
		/// Updates one existing row in the database based on the primary key and marks the entity as <see cref="EntityState.Synchronized"/>.
		/// </summary>
		/// <returns>Number of rows affected.</returns>
		/// <remarks><p>Copies the values from this entity to the row identified by the primary key.</p>
		/// <p>Throws exception if parent table doesn't have a primary key constraint or if the primary key fields of this entity are not set.</p></remarks>
		/// <example>
		/// The following example fetches an existing entity from the database, changes some of it's fields and saves it back to the database.
		/// <code>
		/// public void UpdateLastName(int employeeId, string newLastName)
		/// {
		/// 	// We only want to change one field (LastName).
		/// 	// Therefore, to keep the other fields intact, original entity must be fetched.
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 	emp.EmployeeID = employeeId;
		/// 	EntityDAO dao = new EntityDAO(emp);
		/// 	dao.SelectOne();
		/// 
		/// 	// Change the last name and save the changes to database.
		/// 	emp.LastName = newLastName;
		/// 	dao.UpdateOne();
		/// }
		/// </code>
		/// Original entity values don't have to be fetched if all of the new values are known.
		/// The following example updates an existing entity, but it accesses the database only once.
		/// <code>
		/// public void UpdateJohnSmith()
		/// {
		/// 	// Update a row that has EmployeeID of 1 with new values.
		/// 	// The old entity doesn't have to be retrieved from the database if we know all of the new values.
		/// 	EmployeesEntity johnSmith = new EmployeesEntity();
		/// 	johnSmith.EmployeeID = 1;
		/// 	johnSmith.FirstName = "Jonathan";
		/// 	johnSmith.LastName = "Smith";
		/// 	
		/// 	EntityDAO dao = new EntityDAO(johnSmith);
		/// 	dao.UpdateOne();
		/// }
		/// </code>
		/// </example>
		#endregion
		public virtual int UpdateOne()
		{
            CommandExecutionStatistics lastExecutedCommandInfo;
            int numberOfRowsAffected = DaoUtil.UpdateOne(this.ConnectionProvider, this.Entity, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return numberOfRowsAffected;
		}		

		#region Documentation.
		/// <summary>
		/// Persists entity. Operation (insert, update, delete, nothing) is chosen based on the current <see cref="EntityState"/>.
		/// </summary>
		/// <returns>True if data access operation was executed; false otherwise.</returns>
		/// <remarks>Executed action and returned value depends on the current <see cref="EntityState"/>:
		/// <list type="bullet">
		/// <item><see cref="EntityState.New"/>. Executes <see cref="InsertOne"/> method. Returns <b>true</b>.</item>
		/// <item><see cref="EntityState.Synchronized"/>. No action is executed. Returns <b>false</b>.</item>
		/// <item><see cref="EntityState.OutOfSync"/>. Executes <see cref="UpdateOne"/> method. Returns <b>true</b>.</item>
		/// <item><see cref="EntityState.PendingDeletion"/>. Executes <see cref="DeleteOne"/> method. Returns <b>true</b>.</item>
		/// <item><see cref="EntityState.Deleted"/>. No action is executed. Returns <b>false</b>.</item>
		/// </list>
		/// </remarks>
		#endregion
		public virtual bool Save()
		{
            CommandExecutionStatistics lastExecutedCommandInfo;
            bool isPersisted = DaoUtil.Save(this.ConnectionProvider, this.Entity, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return isPersisted;
		}

		#endregion		

        #region Fetch parents.

        #region Documentation.
        /// <summary>Selects one existing entity from the database based on the Primary Key and populates entity with the retrieved data.</summary>
        /// <param name="parentsToFetch">Relations (foreign keys) to parents which are to be fetched. Child table 
        /// of the given FK must be the current entity's table; otherwise an exception is generated.</param>
        /// <returns>True if select succeeded; false otherwise.</returns>
        #endregion
        public virtual bool SelectOne(params DbRelation[] parentsToFetch)
        {
            CommandExecutionStatistics lastExecutedCmdStats;
            bool exists;
            if (Settings.ParentsFetchedInSingleSelect)
            {
                exists = DaoUtil.SelectOneWithParents(this.ConnectionProvider, this.Entity, parentsToFetch, out lastExecutedCmdStats);
            }
            else
            {
                exists = DaoUtil.SelectOne(this.ConnectionProvider, this.Entity, parentsToFetch, out lastExecutedCmdStats);
            }

            this.LastExecutedCommandInfo = lastExecutedCmdStats;
            return exists;
        }

        #region Documents.
        /// <summary>Selects an existing and partially initializes parent entities.</summary>
        /// <param name="parentsToFetch">Relations (foreign keys) to parents which are to be fetched. Child table 
        /// of the given FK must be the current entity's table;  otherwise an exception is generated.</param>
        /// <param name="parentColumnsToFetch">Fields that belong to parent tables. Only specified parent fields will be fetched and initialized.</param>
        /// <returns>True if select succeeded; false otherwise.</returns>
        /// <example><code>
        /// public OrdersEntity FetchOrderWithPartialParents(int orderId)
        /// {
        ///     OrdersEntity order = new OrdersEntity() { OrderID = orderId };
        ///     IDbColumn[] parentFields = new IDbColumn[]
        ///     {
        ///         new EmployeesMeta().LastName,
        ///         new EmployeesMeta().FirstName,
        ///         new ShippersMeta().CompanyName
        ///     };
        /// 
        ///     new EntityDAO(order).SelectOne(order.Table.ForeignKeys, parentFields);
        ///     return order;
        /// }
        /// </code></example>
        #endregion
        public virtual bool SelectOne(IEnumerable<DbRelation> parentsToFetch, IEnumerable<IDbColumn> parentColumnsToFetch)
        {
            CommandExecutionStatistics lastExecutedCmdStats;
            bool exists = DaoUtil.SelectOneWithPartialParents(this.ConnectionProvider, this.Entity, parentsToFetch, parentColumnsToFetch, out lastExecutedCmdStats);
            this.LastExecutedCommandInfo = lastExecutedCmdStats;
            return exists;
        }

        #region Documentation.
        /// <summary>Fetches a parent entity defined by the foreign key from the data source and stores in into current entity.</summary>
        /// <param name="foreignKey">FK. Child table of the given FK must be the current entity's table; 
        /// otherwise an exception is generated.</param>
        /// <returns>Parent entity. Parent entity. <b>Null</b> if the FK fields haven't been set or if the parent entity with the given key values doesn't exist.</returns>
        /// <remarks>Entity members are affected only if the select operation succeeded.</remarks>
        /// <example><code>
        /// public EmployeesEntity NavigateFromOrderDetailToManager(int orderId, int productId)
        /// { 
        ///     EmployeesEntity manager = null;
        /// 
        ///     OrderDetailsEntity item = new OrderDetailsEntity();
        ///     item.OrderID = orderId;
        ///     item.ProductID = productId;
        /// 
        ///     // SelectOne returns true if the item with specified PK values exists.
        ///     bool itemExists = new EntityDAO(item).SelectOne();
        ///     if (itemExists)
        ///     {
        ///         // Navigate through Orders (use Order Details.OrderID FK), Employees (via Orders.EmployeeID FK)
        ///         // to managers which are also in the Employees table (via Employees.ReportsTo FK).
        ///         new EntityDAO(item).FetchParent(item.Table.FK_OrderID);
        ///         new EntityDAO(item.OrdersParent).FetchParent(item.OrdersParent.Table.FK_EmployeeID);
        ///         new EntityDAO(item.OrdersParent.EmployeesParent).FetchParent(item.OrdersParent.EmployeesParent.Table.FK_ReportsTo);
        ///         manager = item.OrdersParent.EmployeesParent.EmployeesSuperior;
        ///     }
        /// 
        ///     return manager;
        /// }
        /// </code></example>
        #endregion
        public virtual IEntity FetchParent(DbRelation foreignKey)
        {
            CommandExecutionStatistics lastExecutedCommandInfo;
            IEntity parent = DaoUtil.FetchParent(this.ConnectionProvider, this.Entity, foreignKey, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return parent;
        }

        #region Documentation.
        /// <summary>Fetches parent entities defined by the given relations from the data source and stores them into current entity.</summary>
        /// <param name="parentsToFetch">Relations (foreign keys) to parents which are to be fetched. Child table 
        /// of the given FK must be the current entity's table; otherwise an exception is generated.</param>
        /// <returns>Parent entities. Some may be <b>null</b> if the required FK fields haven't been set or if the entities 
        /// with the given key values don't exist.</returns>
        /// <remarks>Preffer using this method instead of a sequence of <see cref="FetchParent(DbRelation)"/> method
        /// invocations. Future optimizers will have a better chance to speed up data access when they know in ahead
        /// what data is to be fetched.</remarks>
        #endregion
        public virtual IEntity[] FetchParents(params DbRelation[] parentsToFetch)
        {
            IEntity[] parents = new IEntity[parentsToFetch.Length];
            CommandExecutionStatistics lastExecutedCmdStats;

            // SelectOne overwrites current state. This can be permitted only if the entity hasn't changed after it has been fetched.
            bool mayOverwriteEntityWOptimizedRoutine = (Settings.ParentsFetchedInSingleSelect && this.Entity.EntityState == EntityState.Synchronized && parentsToFetch.Length > 0);
            if (mayOverwriteEntityWOptimizedRoutine)
            {
                DaoUtil.SelectOneWithParents(this.ConnectionProvider, this.Entity, parentsToFetch, out lastExecutedCmdStats);
                for (int parentIdx = 0; parentIdx < parents.Length; parentIdx++)
                    parents[parentIdx] = this.Entity.GetParent(parentsToFetch[parentIdx]);
            }
            else
            {
                parents = DaoUtil.FetchParents(this.ConnectionProvider, this.Entity, parentsToFetch, out lastExecutedCmdStats);
            }

            this.LastExecutedCommandInfo = lastExecutedCmdStats;
            return parents;
        }

        #endregion		

        #region 1:N children.

        #region Documentation.
        /// <summary>
        /// Fetches child entities that reference the current entity.
        /// </summary>
        /// <param name="destination">A collection to fill with entities built from data. Will contain entities from the child table whose foreign key matches this entity's primary key.</param>
        /// <param name="relation">Relation between the current entity's table and another (child) table. 
        /// Parent table of the given relation must be this entity's table; otherwise an exception is generated.</param>
        /// <param name="childRowsFilter">Filters which child table records are to be returned. Typically used to filter out inactive records.</param>
        /// <param name="orderBy">Specifies the order in which the entities are added to output collection.</param>
        /// <remarks>The method uses a <see cref="SearchCondition"/> to select only the child rows where the specified criteria is matched.
        /// Very often a database is designed so that the child entities are not deleted from the database but are 
        /// marked as deleted instead. This method may be used when only child rows with a specific status value 
        /// (eg. STATUS = 1) are to be fetched.</remarks>
        #endregion
        public virtual void FetchChildren(IList destination, DbRelation relation, SearchCondition childRowsFilter, OrderByClause orderBy)
        {
            CommandExecutionStatistics lastExecutedCommandInfo;
            DaoUtil.FetchChildren(destination, this.ConnectionProvider, this.Entity, relation, childRowsFilter, orderBy, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
        }

        #region Documentation.
        /// <summary>
        /// Fetches child entities that reference the current entity.
        /// </summary>
        /// <param name="relation">Relation between the current entity's table and another (child) table. 
        /// Parent table of the given relation must be this entity's table; otherwise an exception is generated.</param>
        /// <param name="childRowsFilter">Filters which child table records are to be returned. Typically used to filter out inactive records.</param>
        /// <param name="orderBy">Specifies the order in which the entities are added to output collection.</param>
        /// <returns>Rows from the child table that reference the current entity and match the given criteria.</returns>
        /// <remarks>The method uses a <see cref="SearchCondition"/> to select only the child rows where the specified criteria is matched.
        /// Very often a database is designed so that the child entities are not deleted from the database but are 
        /// marked as deleted instead. This method may be used when only child rows with a specific status value 
        /// (eg. STATUS = 1) are to be fetched.</remarks>
        /// <example>
        /// The following example fetches a region and all territories it contains.
        /// <code>
        /// RegionEntity easternRegion = new RegionEntity();
        /// easternRegion.RegionID = 1;
        /// EntityDAO daoRegions = new EntityDAO(easternRegion);
        /// 
        /// // Parent (region) and children (territories) data are fetched using the same connection.
        /// daoRegions.ConnectionProvider.OpenConnection();
        /// 
        /// // Fetch region and children defined by the Territories.RegionID foreign key.
        /// daoRegions.SelectOne();
        /// DataTable easternTerritories = daoRegions.FetchChildren(new TerritoriesMeta().FK_RegionID, null, null);
        /// 
        /// // Explicitly close the connection that was openned manually and return it to the connection pool.
        /// easternRegion.ConnectionProvider.CloseConnection();
        /// </code>
        /// </example>
        #endregion
        public virtual DataTable FetchChildren(DbRelation relation, SearchCondition childRowsFilter, OrderByClause orderBy)
        {
            CommandExecutionStatistics lastExecutedCommandInfo;
            DataTable children = DaoUtil.FetchChildren(this.ConnectionProvider, this.Entity, relation, childRowsFilter, orderBy, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return children;
        }

        #endregion

        #region M:N children.

        #region Documentation.
        /// <summary>
        /// Fetches child entities that reference the current entity.
        /// </summary>
        /// <param name="destination">A collection to fill with entities built from data. Will contain entities from the child table whose foreign key matches this entity's primary key.</param>
        /// <param name="mnRelation">Many-to-many relation. Current entity's table acts as a parent table,
        /// the other primary table in the relation acts as a child. May be recursive.</param>
        /// <param name="childRowsFilter">Filters which child table records are to be returned. Typically used to filter out inactive records.</param>
        /// <param name="orderBy">Specifies the order in which the entities are added to output collection.</param>
        /// <remarks><p>The method uses a <see cref="SearchCondition"/> that compares the specified column to 
        /// the given value. Only the child rows that match the specified criteria are selected. 
        /// Very often database is designed so that the child entities are not deleted from the database but are 
        /// marked as deleted instead. This method may be used when only child rows with a specific status value 
        /// (eg. STATUS = 1) are to be fetched. In addition, a SearchCondition may be created which filters out rows 
        /// from the junction table. See <see cref="ManyToManyRelation.JunctionRowsFilter"/> for more information.</p>
        /// <p>When a single database table is used as both the first and the second primary table of an M:N 
        /// relation then it must be explicitly specified which one is used as a parent and which one as a child table.</p></remarks>
        /// <example>
        /// The following example fetches children entities using a many-to-many relation.
        /// <code>
        /// public EntityCollection&lt;EmployeesEntity, EmployeesMeta&gt; FetchEmployeeTerritories(EmployeesEntity employee)
        /// {			
        /// 	// Fetch territories that the given employee is in charge of.
        /// 
        /// 	// An employee can be in charge of multiple territories.
        /// 	// Multiple employees can be in charge of a single territory.
        /// 	// Employees and Territories table are related by creating junction rows in EmployeeTerritories table.
        /// 	// EmployeeTerritories table has a primary key which is composed of two foreign keys.
        /// 	// These two one-to-many (1:N) relations can be modeled as a one many-to-many (M:N) relation.
        /// 
        /// 	EmployeeTerritoriesMeta junctionTable = new EmployeeTerritoriesMeta();
        /// 	ManyToManyRelation mnRelation = new ManyToManyRelation(junctionTable.FK_EmployeeID, junctionTable.FK_TerritoryID);
        /// 
        ///		EntityDAO daoEmployees = new EntityDAO(employee);
        ///		var territories = new EntityCollection&lt;EmployeesEntity, EmployeesMeta&gt;();
        /// 	daoEmployees.FetchChildren(territories, mnRelation, null, null, null);
        /// 	return territories;
        /// }
        /// </code>
        /// </example>
        #endregion
        public virtual void FetchChildren(IList destination, ManyToManyRelation mnRelation, SearchCondition childRowsFilter, OrderByClause orderBy)
        {
            if (mnRelation.IsRecursive)
                throw new ArgumentException(Messages.EntityDAO_CantAutoDecideWhichTableIsParentInMnRelation, "mnRelation");

            PrimaryTableIndex parentSpecifier;
            if (this.Table.TableName == mnRelation.FirstPrimaryTable.TableName)
                parentSpecifier = PrimaryTableIndex.FirstPrimaryTable;
            else
                parentSpecifier = PrimaryTableIndex.SecondPrimaryTable;

            FetchChildren(destination, mnRelation, parentSpecifier, childRowsFilter, orderBy);
        }

        #region Documentation.
        /// <summary>
        /// Fetches child entities that reference the current entity.
        /// </summary>
        /// <param name="destination">A collection to fill with entities built from data. Will contain entities from the child table whose foreign key matches this entity's primary key.</param>
        /// <param name="mnRelation">Many-to-many relation. Current entity's table acts as a parent table,
        /// the other primary table in the relation acts as a child. May be recursive.</param>
        /// <param name="parentSpecifier">Indicates which of the two primary tables in the M:N relation acts as a parent.</param>
        /// <param name="childRowsFilter">Filters which child table records are to be returned. Typically used to filter out inactive records.</param>
        /// <param name="orderBy">Specifies the order in which the entities are added to output collection.</param>
        /// <remarks><p>The method uses a <see cref="SearchCondition"/> that compares the specified column to 
        /// the given value. Only the child rows that match the specified criteria are selected. 
        /// Very often database is designed so that the child entities are not deleted from the database but are 
        /// marked as deleted instead. This method may be used when only child rows with a specific status value 
        /// (eg. STATUS = 1) are to be fetched. In addition, a SearchCondition may be created which filters out rows 
        /// from the junction table. See <see cref="ManyToManyRelation.JunctionRowsFilter"/> for more information.</p>
        /// <p>When a single database table is used as both the first and the second primary table of an M:N 
        /// relation then it must be explicitly specified which one is used as a parent and which one as a child table.</p></remarks>
        #endregion
        public virtual void FetchChildren(IList destination, ManyToManyRelation mnRelation, PrimaryTableIndex parentSpecifier, SearchCondition childRowsFilter, OrderByClause orderBy)
        {
            SearchCondition junctionFilter;
            DbRelation aliasFreeRelation;
            IDbTable childTable;
            DaoUtil.CreateChildToJunctionRelationAndFilter(this.Entity, mnRelation, parentSpecifier, childRowsFilter, out childTable, out junctionFilter, out aliasFreeRelation);

            IEntityDAO daoChildren = GetDAO(childTable.NewEntity());
            daoChildren.SetSorter(orderBy);
            daoChildren.Select(destination, new RelationBucket(aliasFreeRelation), junctionFilter, -1);
            this.LastExecutedCommandInfo = daoChildren.LastExecutedCommandInfo;
        }

        #region Documentation.
        /// <summary>
        /// Fetches child entities that reference the current entity.
        /// </summary>
        /// <param name="mnRelation">Many-to-many relation. Current entity's table acts as a parent table,
        /// the other primary table in the relation acts as a child. May be recursive.</param>
        /// <param name="childRowsFilter">Filters which child table records are to be returned. Typically used to filter out inactive records.</param>
        /// <param name="orderBy">Specifies the order in which the entities are added to output collection.</param>
        /// <remarks><p>The method uses a <see cref="SearchCondition"/> that compares the specified column to 
        /// the given value. Only the child rows that match the specified criteria are selected. 
        /// Very often database is designed so that the child entities are not deleted from the database but are 
        /// marked as deleted instead. This method may be used when only child rows with a specific status value 
        /// (eg. STATUS = 1) are to be fetched. In addition, a SearchCondition may be created which filters out rows 
        /// from the junction table. See <see cref="ManyToManyRelation.JunctionRowsFilter"/> for more information.</p>
        /// <p>When a single database table is used as both the first and the second primary table of an M:N 
        /// relation then it must be explicitly specified which one is used as a parent and which one as a child table.</p></remarks>
        /// <example>
        /// The following example fetches children entities using a many-to-many relation.
        /// <code>
        /// public DataTable FetchEmployeeTerritories(EmployeesEntity employee)
        /// {			
        /// 	// Fetch territories that the given employee is in charge of.
        /// 
        /// 	// An employee can be in charge of multiple territories.
        /// 	// Multiple employees can be in charge of a single territory.
        /// 	// Employees and Territories table are related by creating junction rows in EmployeeTerritories table.
        /// 	// EmployeeTerritories table has a primary key which is composed of two foreign keys.
        /// 	// These two one-to-many (1:N) relations can be modeled as a one many-to-many (M:N) relation.
        /// 
        /// 	EmployeeTerritoriesMeta junctionTable = new EmployeeTerritoriesMeta();
        /// 	ManyToManyRelation mnRelation = new ManyToManyRelation(junctionTable.FK_EmployeeID, junctionTable.FK_TerritoryID, null, null);
        /// 
        ///		EntityDAO daoEmployees = new EntityDAO(employee);
        /// 	DataTable territories = daoEmployees.FetchChildren(mnRelation);
        /// 	return territories;
        /// }
        /// </code>
        /// </example>
        #endregion
        public virtual DataTable FetchChildren(ManyToManyRelation mnRelation, SearchCondition childRowsFilter, OrderByClause orderBy)
        {
            if (mnRelation.IsRecursive)
                throw new ArgumentException(Messages.EntityDAO_CantAutoDecideWhichTableIsParentInMnRelation, "mnRelation");

            PrimaryTableIndex parentSpecifier;
            if (this.Table.TableName == mnRelation.FirstPrimaryTable.TableName)
                parentSpecifier = PrimaryTableIndex.FirstPrimaryTable;
            else
                parentSpecifier = PrimaryTableIndex.SecondPrimaryTable;

            return FetchChildren(mnRelation, parentSpecifier, childRowsFilter, orderBy);
        }

        #region Documentation.
        /// <summary>
        /// Fetches child entities that reference the current entity.
        /// </summary>
        /// <param name="mnRelation">Many-to-many relation. Current entity's table acts as a parent table,
        /// the other primary table in the relation acts as a child. May be recursive.</param>
        /// <param name="parentSpecifier">Indicates which of the two primary tables in the M:N relation acts as a parent.</param>
        /// <param name="childRowsFilter">Filters which child table records are to be returned. Typically used to filter out inactive records.</param>
        /// <param name="orderBy">Specifies the order in which the entities are added to output collection.</param>
        /// <remarks><p>The method uses a <see cref="SearchCondition"/> that compares the specified column to 
        /// the given value. Only the child rows that match the specified criteria are selected. 
        /// Very often database is designed so that the child entities are not deleted from the database but are 
        /// marked as deleted instead. This method may be used when only child rows with a specific status value 
        /// (eg. STATUS = 1) are to be fetched. In addition, a SearchCondition may be created which filters out rows 
        /// from the junction table. See <see cref="ManyToManyRelation.JunctionRowsFilter"/> for more information.</p>
        /// <p>When a single database table is used as both the first and the second primary table of an M:N 
        /// relation then it must be explicitly specified which one is used as a parent and which one as a child table.</p></remarks>
        #endregion
        public virtual DataTable FetchChildren(ManyToManyRelation mnRelation, PrimaryTableIndex parentSpecifier, SearchCondition childRowsFilter, OrderByClause orderBy)
        {
            SearchCondition junctionFilter;
            DbRelation aliasFreeRelation;
            IDbTable childTable;
            DaoUtil.CreateChildToJunctionRelationAndFilter(this.Entity, mnRelation, parentSpecifier, childRowsFilter, out childTable, out junctionFilter, out aliasFreeRelation);

            IEntityDAO daoChildren = GetDAO(childTable.NewEntity());
            daoChildren.SetSorter(orderBy);
            DataTable children = daoChildren.Select(new RelationBucket(aliasFreeRelation), junctionFilter, -1);
            this.LastExecutedCommandInfo = daoChildren.LastExecutedCommandInfo;
            return children;
        }

        #endregion

        #region Hierarchical queries and updates.

        private IHierarchicalQueryExecutor GetTreeExecutor()
        {
            IHierarchicalQueryExecutor executor = DbmsComponentFactory.GetComponent<IHierarchicalQueryExecutor>(this.ConnectionProvider.DBMS);
            return executor;
        }

        #region Documentation.
        /// <summary>
        /// Selects rows in the hierarchy under the current entity.
        /// </summary>
        /// <param name="recursiveRelation">Recursive relation which defines a hierarchy.</param>
        /// <param name="leafFilter">The search condition which restricts the rows returned. Often a criteria which filters out deactivated records.</param>
        /// <param name="beginAtLevel">Zero-based index of the first level to fetch. Zero indicates that fetching starts at current entity,
        /// one indicates that fetching starts at the level directly beneath it etc.</param>
        /// <param name="endAtLevel">Zero-based index of the last level to fetch. Zero indicates that fetching ends at current entity,
        /// one indicates that fetching ends at the level directly beneath it etc. <see cref="System.Int32.MaxValue"/> indicates that operation ends at leaf nodes.</param>
        /// <returns>Rows in the hierarchy under the current entity.</returns>
        /// <example>The following example fetches all rows in the employee hierarchy under the specified employee/manager.
        /// <code>
        /// public DataTable FetchHierarchy(int managerId)
        /// {
        /// 	EmployeesEntity manager = new EmployeesEntity(managerId);
        /// 	IEntityDAO dao = new EntityDAO(manager);
        /// 	DataTable hierarchy = dao.SelectTree(manager.Table.FK_ReportsTo, null, 1 int.MaxValue);
        /// 	return hierarchy;
        /// }
        /// </code>
        /// </example>
        #endregion
        public virtual DataTable SelectTree(DbRelation recursiveRelation, SearchCondition leafFilter, int beginAtLevel, int endAtLevel)
        {
            return GetTreeExecutor().SelectTree(this.ConnectionProvider, this.Entity, recursiveRelation, leafFilter, Sorter, SortOnDatabase, beginAtLevel, endAtLevel);
        }

        #region Documentation.
        /// <summary>
        /// Counts the number of levels in the hieararchy starting with the current entity.
        /// </summary>
        /// <param name="recursiveRelation">Recursive relation which defines a hierarchy.</param>
        /// <returns>Sub-tree depth.</returns>
        /// <example>The following example returns the hierarchy depth, ie. the number of levels in th sub-tree.
        /// <code>
        /// public int FetchHierarchyDepth(int managerId)
        ///	{
        ///		EmployeesEntity manager = new EmployeesEntity(managerId);
        ///		IEntityDAO dao = new EntityDAO(manager);
        ///		int depth = dao.DetermineTreeDepth(manager.Table.FK_ReportsTo);
        ///		return depth;
        ///	}
        /// </code>
        /// </example>
        #endregion
        public virtual int DetermineTreeDepth(DbRelation recursiveRelation)
        {
            return GetTreeExecutor().DetermineTreeDepth(this.ConnectionProvider, this.Entity, recursiveRelation);
        }

        #region Documentation.
        /// <summary>
        /// Updates rows in the hierarchy under the current entity.
        /// </summary>
        /// <param name="recursiveRelation">Recursive relation which defines a hierarchy.</param>
        /// <param name="setExpressions">The list of columns to be updated and expressions that compute/contain the new values.</param>
        /// <param name="leafFilter">The search condition which limits the number of rows that are updated. Often a criteria which filters out deactivated records.</param>
        /// <param name="beginAtLevel">Zero-based index of the first level to update. Zero indicates that updating starts at current entity,
        /// one indicates that updating starts at the level directly beneath it etc.</param>
        /// <param name="endAtLevel">Zero-based index of the last level to update. Zero indicates that updating ends at current entity,
        /// one indicates that updating ends at the level directly beneath it etc. <see cref="System.Int32.MaxValue"/> indicates that operation ends at leaf nodes.</param>
        /// <returns>Number of rows affected.</returns>
        /// <example>The following example updates all rows in the employee hierarchy under the specified employee/manager.
        /// <code>
        /// public int MoveHierarchyToCountry(int managerId, string newCountry)
        ///	{
        ///		EmployeesEntity manager = new EmployeesEntity(managerId);
        ///		UpdateList setNewCountry = new UpdateList(manager.Table.Country, newCountry);
        ///		IEntityDAO dao = new EntityDAO(manager);
        ///		int rowsAffected = dao.UpdateTree(manager.Table.FK_ReportsTo, setNewCountry, null, 1, int.MaxValue);
        ///		return rowsAffected;
        ///	}
        /// </code>
        /// </example>
        #endregion
        public virtual int UpdateTree(DbRelation recursiveRelation, UpdateList setExpressions, SearchCondition leafFilter, int beginAtLevel, int endAtLevel)
        {
            return GetTreeExecutor().UpdateTree(this.ConnectionProvider, this.Entity, recursiveRelation, setExpressions, leafFilter, beginAtLevel, endAtLevel);
        }

        #region Documentation.
        /// <summary>
        /// Counts rows in the hierarchy under the current entity.
        /// </summary>
        /// <param name="recursiveRelation">Recursive relation which defines a hierarchy.</param>
        /// <param name="leafFilter">The search condition which restricts the rows counted. Often a criteria which filters out deactivated records.</param>
        /// <param name="beginAtLevel">Zero-based index of the first level to process. Zero indicates that counting starts at current entity,
        /// one indicates that counting starts at the level directly beneath it etc.</param>
        /// <param name="endAtLevel">Zero-based index of the last level to process. Zero indicates that counting ends at current entity,
        /// one indicates that counting ends at the level directly beneath it etc. <see cref="System.Int32.MaxValue"/> indicates that operation ends at leaf nodes.</param>
        /// <returns>Total row count in the hierarchy.</returns>
        /// <example>The following example counts all rows in the employee hierarchy under the specified employee/manager.
        /// <code>
        ///	public int CountAllSubordinates(int managerId)
        ///	{
        ///		EmployeesEntity manager = new EmployeesEntity(managerId);
        ///		const int beginAtLevelBeneathManager = 1;
        ///		IEntityDAO dao = new EntityDAO(manager);
        ///		int totalEmployeeCount = dao.CountTree(manager.Table.FK_ReportsTo, null, beginAtLevelBeneathManager, int.MaxValue);
        ///		return totalEmployeeCount;
        ///	}
        /// </code>
        /// </example>
        #endregion
        public virtual int CountTree(DbRelation recursiveRelation, SearchCondition leafFilter, int beginAtLevel, int endAtLevel)
        {
            return GetTreeExecutor().CountTree(this.ConnectionProvider, this.Entity, recursiveRelation, leafFilter, beginAtLevel, endAtLevel);
        }

        #region Documentation.
        /// <summary>
        /// Retrieves the sum of the column values in the hierarchy under the current entity.
        /// </summary>
        /// <param name="column">Column. Must be a numeric column that belongs to the current entity's table.</param>
        /// <param name="recursiveRelation">Recursive relation which defines a hierarchy.</param>
        /// <param name="leafFilter">The search condition which restricts the rows processed. Often a criteria which filters out deactivated records.</param>
        /// <param name="beginAtLevel">Zero-based index of the first level to process. Zero indicates that summing starts at current entity,
        /// one indicates that summing starts at the level directly beneath it etc.</param>
        /// <param name="endAtLevel">Zero-based index of the last level to process. Zero indicates that summing ends at current entity,
        /// one indicates that summing ends at the level directly beneath it etc. <see cref="System.Int32.MaxValue"/> indicates that operation ends at leaf nodes.</param>
        /// <returns>Total sum of column values in the hierarchy or <see cref="DBNull.Value"/>.</returns>
        #endregion
        public virtual object SumTree(IDbColumn column, DbRelation recursiveRelation, SearchCondition leafFilter, int beginAtLevel, int endAtLevel)
        {
            return GetTreeExecutor().SumTree(this.ConnectionProvider, this.Entity, column, recursiveRelation, leafFilter, beginAtLevel, endAtLevel);
        }

        #region Documentation.
        /// <summary>
        /// Retrieves the minimum column value in the hierarchy under the current entity.
        /// </summary>
        /// <param name="column">Column to compare. Must belong to the current entity's table.</param>
        /// <param name="recursiveRelation">Recursive relation which defines a hierarchy.</param>
        /// <param name="leafFilter">The search condition which restricts the rows processed. Often a criteria which filters out deactivated records.</param>
        /// <param name="beginAtLevel">Zero-based index of the first level to process. Zero indicates that comparison starts at current entity,
        /// one indicates that comparison starts at the level directly beneath it etc.</param>
        /// <param name="endAtLevel">Zero-based index of the last level to process. Zero indicates that comparison ends at current entity,
        /// one indicates that comparison ends at the level directly beneath it etc. <see cref="System.Int32.MaxValue"/> indicates that operation ends at leaf nodes.</param>
        /// <returns>Minimum value or <see cref="DBNull.Value"/>.</returns>
        /// <example>The following example retrieves the date of the oldest employee in the hierarchy under the specified employee/manager.
        /// <code>
        ///	public DateTime FindOldestInHierarchy(int managerId)
        ///	{
        ///		EmployeesEntity manager = new EmployeesEntity(managerId);
        ///		IEntityDAO dao = new EntityDAO(manager);
        ///		object result = dao.MinInTree(manager.Table.BirthDate, manager.Table.FK_ReportsTo, null, 1, int.MaxValue);
        ///		return Convert.ToDateTime(result);
        ///	}
        /// </code>
        /// </example>
        #endregion
        public virtual object MinInTree(IDbColumn column, DbRelation recursiveRelation, SearchCondition leafFilter, int beginAtLevel, int endAtLevel)
        {
            return GetTreeExecutor().MinInTree(this.ConnectionProvider, this.Entity, column, recursiveRelation, leafFilter, beginAtLevel, endAtLevel);
        }

        #region Documentation.
        /// <summary>
        /// Retrieves the maximum column value in the hierarchy under the current entity.
        /// </summary>
        /// <param name="column">Column to compare. Must belong to the current entity's table.</param>
        /// <param name="recursiveRelation">Recursive relation which defines a hierarchy.</param>
        /// <param name="leafFilter">The search condition which restricts the rows processed. Often a criteria which filters out deactivated records.</param>
        /// <param name="beginAtLevel">Zero-based index of the first level to process. Zero indicates that comparison starts at current entity,
        /// one indicates that comparison starts at the level directly beneath it etc.</param>
        /// <param name="endAtLevel">Zero-based index of the last level to process. Zero indicates that comparison ends at current entity,
        /// one indicates that comparison ends at the level directly beneath it etc. <see cref="System.Int32.MaxValue"/> indicates that operation ends at leaf nodes.</param>
        /// <returns>Maximum value or <see cref="DBNull.Value"/>.</returns>
        /// <example>The following example retrieves the date of the oldest employee in the hierarchy under the specified employee/manager.
        /// <code>
        ///	public DateTime FindYoungestInHierarchy(int managerId)
        ///	{
        ///		EmployeesEntity manager = new EmployeesEntity(managerId);
        ///		IEntityDAO dao = new EntityDAO(manager);
        ///		object result = dao.MaxInTree(manager.Table.BirthDate, manager.Table.FK_ReportsTo, null, 1, int.MaxValue);
        ///		return Convert.ToDateTime(result);
        ///	}
        /// </code>
        /// </example>
        #endregion
        public virtual object MaxInTree(IDbColumn column, DbRelation recursiveRelation, SearchCondition leafFilter, int beginAtLevel, int endAtLevel)
        {
            return GetTreeExecutor().MaxInTree(this.ConnectionProvider, this.Entity, column, recursiveRelation, leafFilter, beginAtLevel, endAtLevel);
        }

        #endregion

        // Table methods.

		#region Delete.

		#region Documentation.
		/// <summary>
		/// Deletes all rows that match the filter criteria.
		/// </summary>
		/// <param name="filter">The search condition which limits the number of rows that are deleted. 
		/// Throws exception if <b>null</b> or an empty SearchCondition is passed as an argument.</param>
		/// <returns>Number of rows affected.</returns>
		/// <example>
		/// The following example creates a SearchCondition that filters out all employees born in 1977. 
		/// Delete method uses the SearchCondition to delete all rows that match it.
		/// <code>
		/// public void DeleteEmployeesBornIn1977()
		/// {
		/// 	// Delete all employees that were born between January 1, 1977 and January 1, 1978.
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 	emp.Delete(new SearchCondition(PredicateFactory.Between(emp.Table.BirthDate, new DateTime(1977,1,1), new DateTime(1978,1,1))));			
		/// }
		/// </code>
		/// The following example creates a SearchCondition that filters out all employees that are in charge
		/// of the specified territory. SearchCondition contains an IN predicate that uses a subquery which 
		/// returns all employee IDs linked to the given territory ID. Delete method uses the SearchCondition 
		/// to delete all rows that match it. 
		/// <code>
		/// public void DeleteEmployeesInChargeOfTheGivenTerritory(string territoryId)
		/// {			
		/// 	// This is the equivalent SQL code that does the same task.
		/// 
		/// 	//	DELETE FROM Employees 
		/// 	//	WHERE Employees.EmployeeID IN 
		/// 	//		(SELECT EmployeeTerritories.EmployeeID 
		/// 	//		FROM EmployeeTerritories 
		/// 	//		WHERE EmployeeTerritories.TerritoryID = @territoryId)
		/// 	//				EmployeesEntity emp = new EmployeesEntity();
		/// 
		/// 	// First we construct the subquery.
		/// 	EmployeeTerritoriesMeta empTerritories = new EmployeeTerritoriesMeta();
		/// 	SelectStatement subQuery = new SelectStatement(empTerritories);
		/// 	subQuery.SelectList.Add(empTerritories.EmployeeID);
		/// 	subQuery.Where.Add(PredicateFactory.Compare(empTerritories.TerritoryID, "=", territoryId));			
		/// 
		/// 	// SearchCondition uses the subquery to filter out the rows we want to delete.
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 	SearchCondition inChargeOfTerritory = new SearchCondition(PredicateFactory.In(emp.Table.EmployeeID, subQuery));
		/// 	
		/// 	EntityDAO dao = new EntityDAO(emp);
		/// 	dao.Delete(inChargeOfTerritory);
		/// }		
		/// </code>
		/// </example>
		#endregion
		public virtual int Delete(SearchCondition filter)
		{		
            CommandExecutionStatistics lastExecutedCommandInfo;
            int numberOfRowsAffected = DaoUtil.Delete(this.ConnectionProvider, this.Table, filter, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return numberOfRowsAffected;
		}

		#endregion

        #region Update.

        #region Documentation.
        /// <summary>
        /// Updates all rows that match the filter criteria.
        /// </summary>
        /// <param name="setExpressions">The list of columns to be updated and expressions that compute/contain
        /// the new values. Throws exception if <b>null</b> or an empty UpdateList is passed as an argument.</param>		
        /// <param name="filter">The search condition which limits the number of rows that are updated.
        /// Throws exception if <b>null</b> or an empty SearchCondition is passed as an argument.</param>
        /// <returns>Number of rows affected.</returns>
        /// <remarks>Updates all rows in the table that match the specified filter with values/expressions in the given SET list.</remarks>
        /// <example>
        /// The following example updates rows in the table Employees. Field ReportsTo will be set to <b>null</b> for specified rows.
        /// <code>
        /// public void SetBossToNull(int[] employees)
        /// {
        /// 	// IN predicate expects an array of objects.
        /// 	// int[] must be converted to object[]
        /// 	object[] oEmployeeIds = new object[employees.Length];
        /// 	employees.CopyTo(oEmployeeIds, 0);
        /// 
        /// 	// Employees whose IDs are in the given array will report to no one.
        /// 	EmployeesEntity emp = new EmployeesEntity();
        /// 	UpdateList setBossToNull = new UpdateList(emp.Table.ReportsTo, null);
        /// 	SearchCondition employeesInArray = new SearchCondition(PredicateFactory.In(emp.Table.EmployeeID, oEmployeeIds));
        /// 	
        /// 	EntityDAO dao = new EntityDAO(emp);
        /// 	dao.Update(setBossToNull, employeesInArray);
        /// }
        /// </code>
        /// </example>
        #endregion
        public virtual int Update(UpdateList setExpressions, SearchCondition filter)
        {
            CommandExecutionStatistics lastExecutedCommandInfo;
            int numberOfRowsAffected = DaoUtil.Update(this.ConnectionProvider, this.Table, setExpressions, filter, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return numberOfRowsAffected;
        }

        #endregion

		#region Select.

        #region Documentation.
        /// <summary>
        /// Selects all rows that match the filter criteria. Data is sorted if the sorter is set.
        /// </summary>
        /// <param name="destination">A collection to fill with entities built from data.</param>
        /// <param name="relations">Relations. May be <b>null</b> if the filter doesn't use other tables.</param>
        /// <param name="filter">The search condition which restricts the rows returned.</param>		
        /// <param name="maxNumberOfItemsToReturn">Maximum number of items that will be returned. 
        /// If set to zero or a negative value, all rows matching criteria will be returned.</param>
        /// <example>
        /// The following example uses the Select method that allows advanced filtering using RelationBucket 
        /// and SearchCondition objects. It selects all employees that are in charge of the New York territory.
        /// <code>
        /// public EntityCollection&lt;EmployeesEntity, EmployeesMeta&gt; SelectEmployeesInChargeOfNewYorkTerritory()
        /// {
        /// 	EmployeesEntity emp = new EmployeesEntity();
        /// 
        /// 	// Create a relation between Employees and EmployeeTerritories tables.
        /// 	// Employees is a parent table.
        /// 	// EmployeeTerritories is a child table.
        /// 	// EmployeeTerritories.EmployeeID is a foreign key.
        /// 	EmployeeTerritoriesMeta empTerritories = new EmployeeTerritoriesMeta();
        /// 	RelationBucket relations = new RelationBucket();
        /// 	relations.Add(new DbRelation(emp.Table, empTerritories, empTerritories.EmployeeID));
        /// 
        /// 	// Create a search condition with a single predicate.
        /// 	// Comparisson predicate compares TerritoryID field to given value (New York ID)
        /// 	// using the specified comparison operator ("=").
        /// 	SearchCondition territoryIdEqualsNy = new SearchCondition();
        /// 	territoryIdEqualsNy.Add(PredicateFactory.Compare(empTerritories.TerritoryID, "=", "10019"));
        /// 	
        /// 	// Select all employees that are in charge of the New York territory.
        /// 	EntityDAO dao = new EntityDAO(emp);
        /// 	var nyEmployees = new EntityCollection&lt;EmployeesEntity, EmployeesMeta&gt;();
        /// 	dao.Select(nyEmployees, relations, territoryIdEqualsNy, 0);
        /// 	return nyEmployees;
        /// }
        /// </code>
        /// </example>
        #endregion
        public virtual void Select(IList destination, RelationBucket relations, SearchCondition filter, int maxNumberOfItemsToReturn)
        {
            CommandExecutionStatistics lastExecutedCommandInfo;
            IObjectReader entityReader = EntityReaderFactory.GetEntityReader(destination, this.Entity);
            DaoUtil.Select(destination, entityReader, this.ConnectionProvider, this.Table, relations, filter, maxNumberOfItemsToReturn, this.Sorter, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
        }

		#region Documentation.
		/// <summary>
		/// Selects all rows that match the filter criteria. Data is sorted if the sorter is set.
		/// </summary>
		/// <param name="relations">Relations. May be <b>null</b> if the filter doesn't use other tables.</param>
		/// <param name="filter">The search condition which restricts the rows returned.</param>		
		/// <param name="maxNumberOfItemsToReturn">Maximum number of items that will be returned. 
		/// If set to zero or a negative value, all rows matching criteria will be returned.</param>
		/// <returns>Data that matches the filter.</returns>
		/// <example>
		/// The following example uses the Select method that allows advanced filtering using RelationBucket 
		/// and SearchCondition objects. It selects all employees that are in charge of the New York territory.
		/// <code>
		/// public DataTable SelectEmployeesInChargeOfNewYorkTerritory()
		/// {
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 
		/// 	// Create a relation between Employees and EmployeeTerritories tables.
		/// 	// Employees is a parent table.
		/// 	// EmployeeTerritories is a child table.
		/// 	// EmployeeTerritories.EmployeeID is a foreign key.
		/// 	EmployeeTerritoriesMeta empTerritories = new EmployeeTerritoriesMeta();
		/// 	RelationBucket relations = new RelationBucket();
		/// 	relations.Add(new DbRelation(emp.Table, empTerritories, empTerritories.EmployeeID));
		/// 
		/// 	// Create a search condition with a single predicate.
		/// 	// Comparisson predicate compares TerritoryID field to given value (New York ID)
		/// 	// using the specified comparison operator ("=").
		/// 	SearchCondition territoryIdEqualsNy = new SearchCondition();
		/// 	territoryIdEqualsNy.Add(PredicateFactory.Compare(empTerritories.TerritoryID, "=", "10019"));
		/// 	
		/// 	// Select all employees that are in charge of the New York territory.
		/// 	EntityDAO dao = new EntityDAO(emp);
		/// 	DataTable nyEmployees = dao.Select(relations, territoryIdEqualsNy, 0);
		/// 	return nyEmployees;
		/// }
		/// </code>
		/// </example>
		#endregion
		public virtual DataTable Select(RelationBucket relations, SearchCondition filter, int maxNumberOfItemsToReturn)
		{
		    CommandExecutionStatistics lastExecutedCommandInfo;
            DataTable result = DaoUtil.Select(this.ConnectionProvider, this.Table, relations, filter, maxNumberOfItemsToReturn, this.Sorter, this.SortOnDatabase, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return result;
		}	

		#endregion

        #region Find.

        #region Documentation.
        /// <summary>
        /// Selects all rows that match an automatically created filter based on all properties that have been set.
        /// Predicates are combined by AND operators. Data is sorted if the sorter is set.
        /// </summary>
        /// <param name="destination">A collection to fill with entities built from data.</param>
        /// <param name="maxNumberOfItemsToReturn">Maximum number of items that will be returned. 
        /// If set to zero or a negative value, all rows matching the criteria will be returned.</param>
        /// <example>
        /// The following example select all employees whose first name is "John" and the last name is "Smith".
        /// <code>
        /// public EntityCollection&lt;EmployeesEntity, EmployeesMeta&gt; FindAllJohnSmiths()
        /// {
        /// 	EmployeesEntity emp = new EmployeesEntity();
        /// 	emp.FirstName = "John";
        /// 	emp.LastName = "Smith";
        /// 	
        /// 	EntityDAO dao = new EntityDAO(emp);
        /// 	var johnSmiths = new EntityCollection&lt;EmployeesEntity, EmployeesMeta&gt;();
        /// 	dao.Find(johnSmiths, 0);
        /// 	return johnSmiths;
        /// }
        /// </code>
        /// </example>
        #endregion
        public virtual void Find(IList destination, int maxNumberOfItemsToReturn)
        {
            SearchCondition filter = DaoUtil.CreateFilterFromEntity(this.Entity);
            Select(destination, null, filter, maxNumberOfItemsToReturn);
        }

        #region Documentation.
        /// <summary>
        /// Selects all rows that match an automatically created filter based on all properties that have been set.
        /// Predicates are combined by AND operators. Data is sorted if the sorter is set.
        /// </summary>
        /// <param name="maxNumberOfItemsToReturn">Maximum number of items that will be returned. 
        /// If set to zero or a negative value, all rows matching the criteria will be returned.</param>
        /// <returns>Rows that match the automatically created filter.</returns>
        /// <example>
        /// The following example select all employees whose first name is "John" and the last name is "Smith".
        /// <code>
        /// public DataTable FindAllJohnSmiths()
        /// {
        /// 	EmployeesEntity emp = new EmployeesEntity();
        /// 	emp.FirstName = "John";
        /// 	emp.LastName = "Smith";
        /// 	
        /// 	EntityDAO dao = new EntityDAO(emp);
        /// 	return dao.Find(0);
        /// }
        /// </code>
        /// </example>
        #endregion
        public virtual DataTable Find(int maxNumberOfItemsToReturn)
        {
            SearchCondition filter = DaoUtil.CreateFilterFromEntity(this.Entity);
            return Select(null, filter, maxNumberOfItemsToReturn);
        }

        #region Documentation.
        /// <summary>
        /// Selects all rows that match an automatically created filter based on all properties that have been set. 
        /// Predicates are combined by OR operators. Data is sorted if the sorter is set.
        /// </summary>
        /// <param name="destination">A collection to fill with entities built from data.</param>
        /// <param name="maxNumberOfItemsToReturn">Maximum number of items that will be returned. 
        /// If set to zero or a negative value, all rows matching criteria will be returned.</param>
        #endregion
        public virtual void FindMatchingAnyCriteria(IList destination, int maxNumberOfItemsToReturn)
        {
            SearchCondition filter = DaoUtil.CreateFilterMatchingAnyEntityField(this.Entity);
            Select(destination, null, filter, maxNumberOfItemsToReturn);
        }

        #region Documentation.
        /// <summary>
        /// Selects all rows that match an automatically created filter based on all properties that have been set. 
        /// Predicates are combined by OR operators. Data is sorted if the sorter is set.
        /// </summary>
        /// <param name="maxNumberOfItemsToReturn">Maximum number of items that will be returned. 
        /// If set to zero or a negative value, all rows matching criteria will be returned.</param>
        /// <returns>Rows that match the automatically created filter.</returns>
        #endregion
        public virtual DataTable FindMatchingAnyCriteria(int maxNumberOfItemsToReturn)
        {
            SearchCondition filter = DaoUtil.CreateFilterMatchingAnyEntityField(this.Entity);
            return Select(null, filter, maxNumberOfItemsToReturn);
        }

        #endregion

		#region Aggregate functions.

		#region Documentation.
		/// <summary>
		/// Counts all rows in the entity's parent table that match the filter criteria.
		/// </summary>
		/// <param name="relations">Relations. May be <b>null</b> if the filter doesn't use other tables.</param>
		/// <param name="filter">The search condition which restricts the rows counted.</param>
		/// <returns>Number of rows that match the filter.</returns>
		/// <example>
		/// The following example uses the Count method that allows advanced filtering using RelationBucket 
		/// and SearchCondition objects. It returns the number of employees that are in charge of the New York territory.
		/// <code>
		/// public int CountEmployeesInChargeOfNewYorkTerritory()
		/// {
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 
		/// 	// Create a relation between Employees and EmployeeTerritories tables.
		/// 	// Employees is a parent table.
		/// 	// EmployeeTerritories is a child table.
		/// 	// EmployeeTerritories.EmployeeID is a foreign key.
		/// 	EmployeeTerritoriesMeta empTerritories = new EmployeeTerritoriesMeta();
		/// 	RelationBucket relations = new RelationBucket();
		/// 	relations.Add(new DbRelation(emp.Table, empTerritories, empTerritories.EmployeeID));
		/// 
		/// 	// Create a search condition with a single predicate.
		/// 	// Comparisson predicate compares TerritoryID field to given value (New York ID)
		/// 	// using the specified comparison operator ("=").
		/// 	SearchCondition territoryIdEqualsNy = new SearchCondition();
		/// 	territoryIdEqualsNy.Add(PredicateFactory.Compare(empTerritories.TerritoryID, "=", "10019"));
		/// 	
		/// 	// Count all employees that are in charge of the New York territory.
		/// 	EntityDAO dao = new EntityDAO(emp);
		/// 	int count = dao.Count(relations, territoryIdEqualsNy);
		/// 	return count;
		/// }
		/// </code>
		/// </example>
		#endregion
		public virtual int Count(RelationBucket relations, SearchCondition filter)
		{
            CommandExecutionStatistics lastExecutedCommandInfo;
            int numberOfRowsAffected = DaoUtil.Count(this.ConnectionProvider, this.Table, relations, filter, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return numberOfRowsAffected;
		}

        #region Documentation.
        /// <summary>
		/// Retrieves the maximum value in the given column.
		/// </summary>
		/// <param name="column">Column. Must belong to the current entity's table.</param>
		/// <param name="relations">Relations. May be <b>null</b> if the filter doesn't use other tables.</param>
		/// <param name="filter">The search condition which restricts the rows included.</param>
		/// <returns>Maximum value in the given column. <see cref="DBNull.Value"/> if table doesn't contain any rows or 
        /// if all rows that match the criteria have a <b>null</b> value in the specified field.</returns>       
        /// <example>
        /// <code>
        /// public DateTime FetchYoungest()
        ///{
        ///    EmployeesMeta emp = new EmployeesMeta();
        ///    IEntityDAO dao = new EntityDAO(emp);
        ///    return (DateTime)dao.Max(emp.BirthDate, null, null);
        ///}
        ///</code>
        ///</example>
        #endregion
        public virtual object Max(IDbColumn column, RelationBucket relations, SearchCondition filter)
		{
			AggregateFunction max = AggregateFunctionFactory.Max(column, null);
			return FetchAggregateValue(max, relations, filter);
		}

        #region Documentation.
        /// <summary>
		/// Retrieves the minimum value in the given column.
		/// </summary>
		/// <param name="column">Column. Must belong to the current entity's table.</param>
		/// <param name="relations">Relations. May be <b>null</b> if the filter doesn't use other tables.</param>
		/// <param name="filter">The search condition which restricts the rows included.</param>
		/// <returns>Minimum value in the given column. <see cref="DBNull.Value"/> if table doesn't contain any rows or 
		/// if all rows that match the criteria have a <b>null</b> value in the specified field.</returns>
        /// <example>
        /// <code>
        /// public DateTime FetchOldest(emp)
        ///{
        ///    EmployeesMeta emp = new EmployeesMeta();
        ///    IEntityDAO dao = new EntityDAO();
        ///    return (DateTime)dao.Min(emp.BirthDate, null, null);
        ///}
        ///</code>
        ///</example>        
        #endregion
        public virtual object Min(IDbColumn column, RelationBucket relations, SearchCondition filter)
		{
			AggregateFunction min = AggregateFunctionFactory.Min(column, null);
			return FetchAggregateValue(min, relations, filter);
		}

        #region Documentation.
        /// <summary>
		/// Retrieves the sum of the values in the given column.
		/// </summary>
		/// <param name="column">Column. Must be a numeric column that belongs to the current entity's table.</param>
		/// <param name="relations">Relations. May be <b>null</b> if the filter doesn't use other tables.</param>
		/// <param name="filter">The search condition which restricts the rows included.</param>
		/// <returns>The sum of the values in the given column. <see cref="DBNull.Value"/> if table doesn't contain any rows or 
		/// if all rows that match the criteria have a <b>null</b> value in the specified field.</returns>
        /// <remarks>SUM can be used with numeric columns only. Null values are ignored.</remarks>
        #endregion
        public virtual object Sum(IDbColumn column, RelationBucket relations, SearchCondition filter)
		{
			AggregateFunction sum = AggregateFunctionFactory.Sum(column, null);
			return FetchAggregateValue(sum, relations, filter);
		}

        #region Documentation.
        /// <summary>
		/// Retrieves the average of the values in the given column.
		/// </summary>
		/// <param name="column">Column. Must be a numeric column that belongs to the current entity's table.</param>
		/// <param name="relations">Relations. May be <b>null</b> if the filter doesn't use other tables.</param>
		/// <param name="filter">The search condition which restricts the rows included.</param>
		/// <returns>The average of the values in the given column. <see cref="DBNull.Value"/> if table doesn't contain any rows or 
		/// if all rows that match the criteria have a <b>null</b> value in the specified field.</returns>
		/// <remarks>AVG can be used with numeric columns only. Null values are ignored.</remarks>
        /// <example>
        /// <code>
        /// public DateTime AvgProductPrice(emp)
        ///{
        ///    ProductsMeta prod = new ProductsMeta();
        ///    IEntityDAO dao = new EntityDAO(prod);
        ///    object avg = DAL.GetDao(prod).Avg(prod.UnitPrice, null, null);
        ///    return Convert.ToDecimal(avg);
        ///}
        ///</code>
        ///</example>  
        #endregion
        public virtual object Avg(IDbColumn column, RelationBucket relations, SearchCondition filter)
		{
			AggregateFunction avg = AggregateFunctionFactory.Avg(column, null);
			return FetchAggregateValue(avg, relations, filter);
		}

		private object FetchAggregateValue(AggregateFunction function, RelationBucket relations, SearchCondition filter)
		{
            CommandExecutionStatistics lastExecutedCommandInfo;
            object aggregateValue = DaoUtil.FetchAggregateValue(this.ConnectionProvider, this.Table, function, relations, filter, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
            return aggregateValue;
		}

		#endregion

        // Collection methods.
		
        #region SaveCollection.

        /// <summary>
		/// Persists entities in the collection. Operation (insert, update, delete, nothing) is chosen based on the current <see cref="EntityState"/>.
		/// </summary>
		/// <param name="entitiesToSave">Entities which are to persisted.</param>
		/// <remarks>Preffer using this method instead of a sequence of <see cref="Save()"/> method invocations. 
		/// Future optimizers will have a better chance to speed up data access when they know in ahead what data is to be saved.</remarks>
		public void SaveCollection(IEnumerable entitiesToSave)
		{
            if (ArrayUtil.IsNullOrEmpty(entitiesToSave))
                return;

			// Possible future optimizations: ADO.NET 2.0 batch, SQL Server OpenXML, update-from temp table or table variable.
            if (HasAutoIncrementedField(this.Table))
                SaveCollectionInLoop(entitiesToSave);
            else
                SaveCollection(entitiesToSave, /* no need to fetch auto IDs --> try bulk inserts */ false);
		}

        private static bool HasAutoIncrementedField(IDbTable table)
        {
            foreach (IDbColumn col in table.Columns)
            {
                if (col.AutoIncrement)
                    return true;
            }

            return false;
        }

        private void SaveCollectionInLoop(IEnumerable entitiesToSave)
        {
            bool connIsLocal = !this.ConnectionProvider.IsOpen;
            if (connIsLocal)
                this.ConnectionProvider.OpenConnection();

            try
            {
                foreach (IEntity entity in entitiesToSave)
                {
                    IEntityDAO dao = GetDAO(entity);
                    dao.ConnectionProvider = this.ConnectionProvider;
                    dao.Save();
                    this.LastExecutedCommandInfo = dao.LastExecutedCommandInfo;
                }
            }
            finally
            {
                if (connIsLocal)
                    this.ConnectionProvider.CloseConnection();
            }
        }

        // Performance gains are negligible for small batches.
        internal const int MinEntityCountRequiredToUseBulkSaver = 10;

        /// <summary>
		/// Persists entities in the collection. Operation (insert, update, delete, nothing) is chosen based on the current <see cref="EntityState"/>.
		/// </summary>
		/// <param name="entitiesToSave">Entities which are to persisted.</param>
        /// <param name="fetchAutoIdentityValues">Indicates whether auto-identity values assigned to rows by DB are fetched for new/inserted rows.
        /// Setting this parameter to <b>false</b> may siginificantly improve performance.</param>
		/// <remarks><para>Preffer using this method instead of a sequence of <see cref="Save()"/> method invocations. 
        /// Future optimizers will have a better chance to speed up data access when they know in ahead what data is to be saved.</para>
        /// <para>If the <b>fetchAutoIdentityValues</b> parameter is set to <b>false</b> then this method overload will not try to retrieve
        /// IDs of the inserted records which may yield significantly better performance. If bulk saving for target DBMS is not implemented then 
        /// the logic implemented in <see cref="SaveCollection(IEnumerable)"/> method overload is used.</para>
        /// <para>Bulk saving is not used for small batches, ie. when there are less than <b>5</b> new entities because performance gains 
        /// are negligible in these cases.</para></remarks>
        public void SaveCollection(IEnumerable entitiesToSave, bool fetchAutoIdentityValues)
        {
            if (ArrayUtil.IsNullOrEmpty(entitiesToSave))
                return;

            IBulkSaver bulkSaver = null;
            if (!fetchAutoIdentityValues)
            {
                if (EntityDAO.IsNewEntitiesCountGreaterThan(entitiesToSave, EntityDAO.MinEntityCountRequiredToUseBulkSaver))
                    bulkSaver = DbmsComponentFactory.GetComponent<IBulkSaver>(this.ConnectionProvider.DBMS);
            }

            if (bulkSaver != null)
                bulkSaver.BulkSave(this.ConnectionProvider, this.Table, entitiesToSave);
            else
                SaveCollectionInLoop(entitiesToSave);
        }

        internal static bool IsNewEntitiesCountGreaterThan(IEnumerable entities, int stopAtCount)
        {
            int count = 0;
            foreach (IEntity entity in entities)
            {
                if (entity.EntityState == EntityState.New)
                    count++;

                if (count >= stopAtCount)
                    return true;
            }

            return false;
        }

		#endregion

		#region FetchParentsForCollection.

		#region Documentation.
		/// <summary>
		/// Fetches parent entities defined by the given relation from the data source and stores them into provided child entities.
		/// </summary>
		/// <param name="childEntities">Children whose FK properties define which parents are to be fetched.</param>
		/// <param name="parentsToFetch">FK. Relation to parent table from which parent records are to be fetched.</param>
		/// <returns>Parents retrieved from the data source.</returns>
		/// <remarks><p>Child entities with equal FK values will share the same parent object instance (same reference).</p>
		/// <p>Preffer using this method instead of a sequence of <see cref="FetchParent(DbRelation)"/> 
		/// method invocations for each child entity in the collection. This method is optimized and may yield 
		/// significantly better performance.</p></remarks>
		/// <example>The following example fetches a collection of entities from Employees table and then
		/// fetches their parent objects (supervisors) defined by the provided relation.
		/// <code>
		/// public EmployeesCollection FetchEmployeesWithSupervisors(params int[] employeeIds)
		///{		
		///		// Fetch requested employees.
		///		EmployeesMeta employees = new EmployeesMeta();
		///		IEntityDAO dao = new EntityDAO(employees);
		///		SearchCondition whereEmployeeIdIn = new SearchCondition(PredicateFactory.In(employees.EmployeeID, employeeIds));			
		///		EmployeesCollection employeesWithSupervisors = (EmployeesCollection)dao.Select(null, whereEmployeeIdIn);
		///
		///		// Fetch supervisors, ie. employees to which employees in the collection report to.
		///		dao.FetchParentsForCollection(employeesWithSupervisors, employees.FK_ReportsTo);
		///
		///		return employeesWithSupervisors;
		///}
		/// </code>
		/// </example>
		#endregion
        public void FetchParentsForCollection(IEnumerable childEntities, DbRelation parentsToFetch)
		{
            CommandExecutionStatistics lastExecutedCommandInfo;
            DaoUtil.FetchParentsForCollection(childEntities, parentsToFetch, true, this.ConnectionProvider, out lastExecutedCommandInfo);
            this.LastExecutedCommandInfo = lastExecutedCommandInfo;
		}

		#endregion

		#region FetchChildrenForCollection.

		#region Documentation.
		/// <summary>
		/// Fetches children which belong to provided parent entities as defined by the given relation.
		/// </summary>
		/// <param name="parentEntities">Parent entities referenced by children which are to be fetched.</param>
		/// <param name="relation">FK. Relation between parent and child tables.</param>
		/// <returns>Array of child collections retrieved from the data source. Child collections are in the
		/// same order as provided parent objects.</returns>
		/// <remarks><p>Child entities with equal FK values will share the same parent object instance (same reference).</p>
        /// <p>Preffer using this method instead of a sequance of <b>FetchChildren</b> 
		/// method invocations for each parent entity in the provided collection. This method is optimized
		/// and may yield significantly better performance.</p>
		/// </remarks>
		/// <example>The following example fetches a collection of entities from the Regions table and then
		/// fetched their children objects defined by the provided relation.
		/// <code>
		/// public void FetchRegionsAndTerritories(int[] regionIds, out RegionCollection regions, out EntityCollection[] territoriesBelongingToRegions)
		///	{
		///		// Fetch requested regions.
		///		RegionMeta regionsTable = new RegionMeta();
		///		IEntityDAO dao = new EntityDAO(regionsTable);
		///		SearchCondition whereRegionIdIn = new SearchCondition(PredicateFactory.In(regionsTable.RegionID, regionIds));
		///		regions = (RegionCollection)dao.Select(null, whereRegionIdIn);
		///
		///		// Fetch territiories that belong to requested regions.
		///		// One territory collection per region. Territory collections are in the same order as regions.
		///		territoriesBelongingToRegions = dao.FetchChildrenForCollection(regions, new TerritoriesMeta().FK_RegionID);
		///	}
		/// </code>
		/// </example>
		#endregion
        public TChildrenCollection[] FetchChildrenForCollection<TChildrenCollection>(IEnumerable parentEntities, DbRelation relation)
            where TChildrenCollection : IList, new()
        { 
            return DaoUtil.FetchChildrenForCollection<TChildrenCollection>(parentEntities, relation, this.ConnectionProvider);
        }

		#endregion
    }
}
